<template>
	<view class="uni-forms" :class="[_class]">
		<form>
			<slot></slot>
		</form>
	</view>
</template>

<script>
import Validator from './validate.js';
import {
	deepCopy,
	getValue,
	isRequiredField,
	setDataValue,
	getDataValue,
	realName,
	isRealName,
	rawData,
	isEqual,
} from './utils.js';

// #ifndef VUE3
// 后续会慢慢废弃这个方法
import Vue from 'vue';
Vue.prototype.binddata = function (name, value, formName) {
	if (formName) {
		this.$refs[formName].setValue(name, value);
	} else {
		let formVm;
		for (let i in this.$refs) {
			const vm = this.$refs[i];
			if (vm && vm.$options && vm.$options.name === 'uniForms') {
				formVm = vm;
				break;
			}
		}
		if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
		formVm.setValue(name, value);
	}
};
// #endif
/**
 * Forms 表单
 * @description 由输入框、选择器、单选框、多选框等控件组成，用以收集、校验、提交数据
 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
 * @property {Object} rules	表单校验规则
 * @property {String} validateTrigger = [bind|submit|blur]	校验触发器方式 默认 submit
 * @value bind		发生变化时触发
 * @value submit	提交时触发
 * @value blur	  失去焦点时触发
 * @property {String} labelPosition = [top|left]	label 位置 默认 left
 * @value top		顶部显示 label
 * @value left	左侧显示 label
 * @property {String} labelWidth	label 宽度，默认 65px
 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
 * @value left		label 左侧显示
 * @value center	label 居中
 * @value right		label 右侧对齐
 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
 * @value undertext	错误信息在底部显示
 * @value toast			错误信息toast显示
 * @value modal			错误信息modal显示
 * @event {Function} submit	提交时触发
 * @event {Function} validate	校验结果发生变化触发
 */
export default {
	name: 'uniForms',
	emits: ['validate', 'submit'],
	options: {
		virtualHost: true,
	},
	props: {
		// 即将弃用
		value: {
			type: Object,
			default() {
				return null;
			},
		},
		// vue3 替换 value 属性
		modelValue: {
			type: Object,
			default() {
				return null;
			},
		},
		// 1.4.0 开始将不支持 v-model ，且废弃 value 和 modelValue
		model: {
			type: Object,
			default() {
				return null;
			},
		},
		// 表单校验规则
		rules: {
			type: Object,
			default() {
				return {};
			},
		},
		//校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
		errShowType: {
			type: String,
			default: 'undertext',
		},
		// 校验触发器方式 默认 bind 取值 [bind|submit]
		validateTrigger: {
			type: String,
			default: 'submit',
		},
		// label 位置，默认 left 取值  top/left
		labelPosition: {
			type: String,
			default: 'left',
		},
		// label 宽度
		labelWidth: {
			type: [String, Number],
			default: '',
		},
		// label 居中方式，默认 left 取值 left/center/right
		labelAlign: {
			type: String,
			default: 'left',
		},
		border: {
			type: Boolean,
			default: false,
		},
		_class: {
			type: String,
			default: '',
		},
	},
	provide() {
		return {
			uniForm: this,
		};
	},
	data() {
		return {
			// 表单本地值的记录，不应该与传如的值进行关联
			formData: {},
			formRules: {},
		};
	},
	computed: {
		// 计算数据源变化的
		localData() {
			const localVal = this.model || this.modelValue || this.value;
			if (localVal) {
				return deepCopy(localVal);
			}
			return {};
		},
	},
	watch: {
		// 监听数据变化 ,暂时不使用，需要单独赋值
		// localData: {},
		// 监听规则变化
		rules: {
			handler: function (val, oldVal) {
				this.setRules(val);
			},
			deep: true,
			immediate: true,
		},
	},
	created() {
		// #ifdef VUE3
		let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata;
		if (!getbinddata) {
			getApp().$vm.$.appContext.config.globalProperties.binddata = function (name, value, formName) {
				if (formName) {
					this.$refs[formName].setValue(name, value);
				} else {
					let formVm;
					for (let i in this.$refs) {
						const vm = this.$refs[i];
						if (vm && vm.$options && vm.$options.name === 'uniForms') {
							formVm = vm;
							break;
						}
					}
					if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
					formVm.setValue(name, value);
				}
			};
		}
		// #endif

		// 子组件实例数组
		this.childrens = [];
		// TODO 兼容旧版 uni-data-picker ,新版本中无效，只是避免报错
		this.inputChildrens = [];
		this.setRules(this.rules);
	},
	methods: {
		/**
		 * 外部调用方法
		 * 设置规则 ，主要用于小程序自定义检验规则
		 * @param {Array} rules 规则源数据
		 */
		setRules(rules) {
			// TODO 有可能子组件合并规则的时机比这个要早，所以需要合并对象 ，而不是直接赋值，可能会被覆盖
			this.formRules = Object.assign({}, this.formRules, rules);
			// 初始化校验函数
			this.validator = new Validator(rules);
		},

		/**
		 * 外部调用方法
		 * 设置数据，用于设置表单数据，公开给用户使用 ， 不支持在动态表单中使用
		 * @param {Object} key
		 * @param {Object} value
		 */
		setValue(key, value) {
			let example = this.childrens.find((child) => child.name === key);
			if (!example) return null;
			this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || []);
			return example.onFieldChange(this.formData[key]);
		},

		/**
		 * 外部调用方法
		 * 手动提交校验表单
		 * 对整个表单进行校验的方法，参数为一个回调函数。
		 * @param {Array} keepitem 保留不参与校验的字段
		 * @param {type} callback 方法回调
		 */
		validate(keepitem, callback) {
			return this.checkAll(this.formData, keepitem, callback);
		},

		/**
		 * 外部调用方法
		 * 部分表单校验
		 * @param {Array|String} props 需要校验的字段
		 * @param {Function} 回调函数
		 */
		validateField(props = [], callback) {
			props = [].concat(props);
			let invalidFields = {};
			this.childrens.forEach((item) => {
				const name = realName(item.name);
				if (props.indexOf(name) !== -1) {
					invalidFields = Object.assign({}, invalidFields, {
						[name]: this.formData[name],
					});
				}
			});
			return this.checkAll(invalidFields, [], callback);
		},

		/**
		 * 外部调用方法
		 * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组，如不传则移除整个表单的校验结果
		 * @param {Array|String} props 需要移除校验的字段 ，不填为所有
		 */
		clearValidate(props = []) {
			props = [].concat(props);
			this.childrens.forEach((item) => {
				if (props.length === 0) {
					item.errMsg = '';
				} else {
					const name = realName(item.name);
					if (props.indexOf(name) !== -1) {
						item.errMsg = '';
					}
				}
			});
		},

		/**
		 * 外部调用方法 ，即将废弃
		 * 手动提交校验表单
		 * 对整个表单进行校验的方法，参数为一个回调函数。
		 * @param {Array} keepitem 保留不参与校验的字段
		 * @param {type} callback 方法回调
		 */
		submit(keepitem, callback, type) {
			for (let i in this.dataValue) {
				const itemData = this.childrens.find((v) => v.name === i);
				if (itemData) {
					if (this.formData[i] === undefined) {
						this.formData[i] = this._getValue(i, this.dataValue[i]);
					}
				}
			}

			if (!type) {
				console.warn('submit 方法即将废弃，请使用validate方法代替！');
			}

			return this.checkAll(this.formData, keepitem, callback, 'submit');
		},

		// 校验所有
		async checkAll(invalidFields, keepitem, callback, type) {
			// 不存在校验规则 ，则停止校验流程
			if (!this.validator) return;
			let childrens = [];
			// 处理参与校验的item实例
			for (let i in invalidFields) {
				const item = this.childrens.find((v) => realName(v.name) === i);
				if (item) {
					childrens.push(item);
				}
			}

			// 如果validate第一个参数是funciont ,那就走回调
			if (!callback && typeof keepitem === 'function') {
				callback = keepitem;
			}

			let promise;
			// 如果不存在回调，那么使用 Promise 方式返回
			if (!callback && typeof callback !== 'function' && Promise) {
				promise = new Promise((resolve, reject) => {
					callback = function (valid, invalidFields) {
						!valid ? resolve(invalidFields) : reject(valid);
					};
				});
			}

			let results = [];
			// 避免引用错乱 ，建议拷贝对象处理
			let tempFormData = JSON.parse(JSON.stringify(invalidFields));
			// 所有子组件参与校验,使用 for 可以使用  awiat
			for (let i in childrens) {
				const child = childrens[i];
				let name = realName(child.name);
				const result = await child.onFieldChange(tempFormData[name]);
				if (result) {
					results.push(result);
					// toast ,modal 只需要执行第一次就可以
					if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
				}
			}

			if (Array.isArray(results)) {
				if (results.length === 0) results = null;
			}
			if (Array.isArray(keepitem)) {
				keepitem.forEach((v) => {
					let vName = realName(v);
					let value = getDataValue(v, this.localData);
					if (value !== undefined) {
						tempFormData[vName] = value;
					}
				});
			}

			// TODO submit 即将废弃
			if (type === 'submit') {
				this.$emit('submit', {
					detail: {
						value: tempFormData,
						errors: results,
					},
				});
			} else {
				this.$emit('validate', results);
			}

			// const resetFormData = rawData(tempFormData, this.localData, this.name)
			let resetFormData = {};
			resetFormData = rawData(tempFormData, this.name);
			callback && typeof callback === 'function' && callback(results, resetFormData);

			if (promise && callback) {
				return promise;
			} else {
				return null;
			}
		},

		/**
		 * 返回validate事件
		 * @param {Object} result
		 */
		validateCheck(result) {
			this.$emit('validate', result);
		},
		_getValue: getValue,
		_isRequiredField: isRequiredField,
		_setDataValue: setDataValue,
		_getDataValue: getDataValue,
		_realName: realName,
		_isRealName: isRealName,
		_isEqual: isEqual,
	},
};
</script>

<style lang="scss">
.uni-forms {
}
</style>
